#!/usr/bin/env python

# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import math
import optparse
import sched
import subprocess
import sys
import time


def fxrange(start, finish, increment=1.0):
    """Like xrange, but with float arguments."""
    steps = int(math.ceil(float(finish - start) / increment))

    if steps < 0:
        raise ValueError

    for i in xrange(steps):
        yield start + i * increment


def hms(seconds):
    hours = int(seconds / (60 * 60))
    seconds -= hours * 60 * 60
    minutes = int(seconds / 60)
    seconds -= minutes * 60
    return '%d:%02d:%02d' % (hours, minutes, seconds)


class PeriodicExperiment(object):
    """Uses the scheduler to run the specified function repeatedly."""
    def __init__(self,
                 scheduler=None,
                 total_duration=8 * 60 * 60,
                 test_interval=60,
                 test_function=None):
        self._scheduler = scheduler
        self._total_duration = total_duration
        self._test_interval = test_interval
        self._test_function = test_function
        self._start = self._scheduler.timefunc()
        self._finish = self._start + self._total_duration

    def Run(self):
        for start_one in fxrange(self._start,
                                 self._finish,
                                 self._test_interval):
            time_remaining = self._finish - start_one
            self._scheduler.enterabs(start_one,
                                     1,     # Priority
                                     self._test_function,
                                     [time_remaining])
        self._scheduler.run()


class ManualExperiment(object):
    """Runs the experiment repeatedly, prompting for input each time."""
    def __init__(self, test_function):
        self._test_function = test_function

    def Run(self):
        try:
            while True:
                self._test_function(0)    # Pass in a fake time remaining
                _ = raw_input('Press return to run the test again.  '
                              'Control-c to exit.')
        except KeyboardInterrupt:
            return

class IperfTest(object):
    def __init__(self, filename, servername, individual_length):
        self._file = file(filename, 'a')
        self._servername = servername
        self._individual_length = individual_length

    def Run(self, remaining):
        """Run iperf, log output to file, and print."""
        iperf = ['iperf',
                 '--client', self._servername,
                 # Transfer time in seconds.
                 '--time', str(self._individual_length),
                 '--reportstyle', 'c' # CSV output
                 ]
        print '%s remaining.  Running %s' % (hms(remaining), ' '.join(iperf))
        result = subprocess.Popen(iperf,
                                  stdout=subprocess.PIPE).communicate()[0]
        print result.rstrip()
        sys.stdout.flush()
        self._file.write(result)
        self._file.flush()

    def teardown(self):
        self._file.close()

def main():
    default_output = 'stability-' + time.strftime('%Y-%m-%d-%H-%M-%S')

    parser = optparse.OptionParser()
    parser.add_option('--server', default=None,
                      help='Machine running the iperf server')
    parser.add_option('--test_interval', default=60 * 5, type='int',
                      help='Interval (in seconds) between tests')
    parser.add_option('--individual_length', default=10, type='int',
                      help='length (in seconds) of each individual test')
    parser.add_option('--total_duration', default=8 * 60 * 60, type='int',
                      help='length (in seconds) for entire test')
    parser.add_option('--output', default=default_output,
                      help='Output file')
    parser.add_option('--manual', default=False, action='store_true',
                      help='Manual mode; wait for input between every test')

    (options, _) = parser.parse_args()

    if not options.server:
        print 'No server specified.  Specify a server with --server=SERVER.'
        exit(2)

    if options.individual_length > options.test_interval:
        print ('The length of a given bandwidth test must be lower than the '
               'interval between tests')
        exit(2)

    s = sched.scheduler(time.time, time.sleep)

    iperf = IperfTest(filename=options.output,
                      servername=options.server,
                      individual_length=options.individual_length)

    if options.manual:
        e = ManualExperiment(test_function=iperf.Run)
    else:
        e = PeriodicExperiment(scheduler=s,
                               total_duration=options.total_duration,
                               test_interval=options.test_interval,
                               test_function=iperf.Run)
    e.Run()
    iperf.teardown()

if __name__ == '__main__':
    main()
